﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

using Rsdn.Framework.Formatting;
using Rsdn.Janus.Framework.Imaging;
using Rsdn.SmartApp;

namespace Rsdn.Janus
{
    /// <summary>
    /// Экспортирует сообщения.
    /// </summary>
    public static class MessageExporter
    {
        #region Private members
        private static readonly string _exportMessageText =
            SR.Forum.ExportMessages.ExportMessage;

        private static readonly string _exportPrepareText =
            SR.Forum.ExportMessages.ExportPrepareMessage;

        private static readonly string[] _smiles =
            new[]
			{
				"beer", "biggrin", "confused", "crash", "down", "frown", "lol",
				"maniac", "no", "shuffle", "smile", "smirk", "sup", "super",
				"user", "wink", "wow", "xz"
			};

        private static Forum _activeForum;

        static MessageExporter()
        {
            _activeForum = ObjectModel.Features.Instance.ActiveFeature as Forum;
        }

        private delegate void ProgressDelegate(int count, int total);
        #endregion

        #region Показ диалога и сбор сообщений
        /// <summary>
        /// Exports worker function.
        /// Started in thread
        /// </summary>
        /// <param name="progressVisualizer">The progress visualizer.</param>
        /// <param name="provider">The provider.</param>
        /// <param name="exportMessageDialog">The export message dialog.</param>
        private static void ExportWorker(IProgressVisualizer progressVisualizer, IServiceProvider provider, ExportMessageDialog exportMessageDialog)
        {
            progressVisualizer.SetProgressText(_exportPrepareText);


            bool unreadMessagesOnly = exportMessageDialog.UnreadMessagesOnly;
            ExportMode exportMode = exportMessageDialog.ExportMode;

            // Prepare msg list
            IList<IMsg> messages = GetMessages(provider, exportMode, unreadMessagesOnly);

            ProgressDelegate progressDelegate =
                delegate(int count, int total)
                {
                    progressVisualizer.SetProgressText(_exportMessageText.FormatStr(count, total));
                    progressVisualizer.ReportProgress(total, count);
                };

            using (FileStream fs = new FileStream(exportMessageDialog.FileName, FileMode.Create))
            {
                switch (exportMessageDialog.ExportFormat)
                {
                    case ExportFormat.Text:
                        Export2Text(provider, messages, fs, progressDelegate);
                        break;
                    case ExportFormat.HTML:
                        Export2HTML(provider, messages, fs, progressDelegate);
                        break;
                    case ExportFormat.MHT:
                        Export2Mht(provider, messages, fs, progressDelegate);
                        break;
                }
            }
        }

        /// <summary>
        /// Gets the messages.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="exportMode">The export mode.</param>
        /// <param name="unreadMessagesOnly">if set to <c>true</c> [unread messages only].</param>
        /// <returns></returns>
        private static IList<IMsg> GetMessages(IServiceProvider provider, ExportMode exportMode, bool unreadMessagesOnly)
        {
            IList<IMsg> messages = null;

            IActiveMessagesService activeMessagesService = provider.GetRequiredService<IActiveMessagesService>();
            switch (exportMode)
            {
                case ExportMode.Messages:
                    {
                        IEnumerable<IMsg> msgs;
                        IEnumerable<IMsg> activeMessages = activeMessagesService.ActiveMessages;

                        if (unreadMessagesOnly)
                        {
                            msgs = activeMessages.Where(msg => !msg.IsRead);
                        }
                        else
                        {
                            msgs = activeMessages;
                        }

                        messages = new List<IMsg>(msgs);
                    }

                    break;

                case ExportMode.Topics:
                    {
                        messages = new List<IMsg>(100);
                        IEnumerable<IMsg> activeMessages = activeMessagesService.ActiveMessages;
                        foreach (var msg in activeMessages)
                        {
                            GetAllChildren(msg.Topic, messages, unreadMessagesOnly);
                        }
                    }

                    break;

                case ExportMode.Forum:
                    {
                        messages = new List<IMsg>(1000);
                        IMsg msgs = _activeForum.Msgs;

                        foreach (IMsg msg in msgs)
                        {
                            GetAllChildren(msg, messages, unreadMessagesOnly);
                        }
                    }

                    break;
            }

            return messages;
        }

        /// <summary>
        /// Exports messages the specified format.
        /// </summary>
        /// <param name="provider">The provider.</param>
        public static void Export(IServiceProvider provider)
        {
            var activeMessagesSvc = provider.GetService<IActiveMessagesService>();
            var canExportMessages = activeMessagesSvc != null && activeMessagesSvc.ActiveMessages.Any();

            var activeForumSvc = provider.GetService<IActiveForumService>();
            var canExportForum = activeForumSvc != null && activeForumSvc.ActiveForum != null;

            using (ExportMessageDialog exportMessageDialog = new ExportMessageDialog(canExportMessages, canExportForum))
            {
                IUIShell uiShell = provider.GetRequiredService<IUIShell>();
                DialogResult dialogResult = exportMessageDialog.ShowDialog(uiShell.GetMainWindowParent());
                if (dialogResult != DialogResult.OK)
                {
                    return;
                }

                Action<IProgressVisualizer> work =
                    delegate(IProgressVisualizer progressVisualizer)
                    {
                        ExportWorker(progressVisualizer, provider, exportMessageDialog);
                    };

                ProgressWorker.Run(provider, false, work);
            }
        }


        /// <summary>
        /// Gets all children.
        /// </summary>
        /// <param name="msg">The MSG.</param>
        /// <param name="list">The list.</param>
        /// <param name="onlyUnread">if set to <c>true</c> [only unread].</param>
        private static void GetAllChildren(IMsg msg, ICollection<IMsg> list, bool onlyUnread)
        {
            if (!onlyUnread || msg.IsUnread)
            {
                list.Add(msg);
            }

            foreach (IMsg child in msg)
            {
                GetAllChildren(child, list, onlyUnread);
            }
        }

        #endregion

        #region Вспомогательные ф-ии

        /// <summary>
        /// Преобразовывает userclass число в строку с подсветкой или без.
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="userClass">The user class.</param>
        /// <param name="isHtml">if set to <c>true</c> [is HTML].</param>
        /// <returns></returns>
        private static string FormatUserClass(IServiceProvider provider, UserClass userClass, bool isHtml)
        {
            IFormatMessageHelperService formatMessageService =
                provider.GetRequiredService<IFormatMessageHelperService>();

            return formatMessageService.FormatUserClass(userClass, isHtml);
        }
        #endregion

        #region Экспорт в текст

        /// <summary>
        /// Export to the text.
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="msgs">The MSGS.</param>
        /// <param name="stream">The stream.</param>
        /// <param name="progressDelegate">The progress delegate.</param>
        private static void Export2Text(IServiceProvider provider, ICollection<IMsg> msgs, Stream stream, ProgressDelegate progressDelegate)
        {
            StringBuilder sb = new StringBuilder();
            int maxCharacters = Config.Instance.ForumExportConfig.CharsPerLine;

            int i = 0;
            foreach (IMsg msg in msgs)
            {
                sb.Append(BuildLimitedWidthString("_", ' ', ' ', maxCharacters));
                sb.Append(BuildLimitedWidthString(
                    SR.TGColumnDate
                    + ": " + msg.Date,
                        '|',
                        '|',
                        maxCharacters));
                sb.Append(BuildLimitedWidthString(
                        SR.TGColumnAuthor
                        + ": " + msg.UserNick + " " + FormatUserClass(provider, (UserClass)msg.UserClass, false),
                        '|',
                        '|',
                        maxCharacters));
                sb.Append(BuildLimitedWidthString(
                        SR.TGColumnSubject
                        + ": " + msg.Subject,
                        '|',
                        '|',
                        maxCharacters));
                if (msg.GetFormattedRating().Length > 0)
                {
                    sb.Append(BuildLimitedWidthString(
                            SR.TGColumnRate
                            + ": " + msg.GetFormattedRating(),
                            '|',
                            '|',
                            maxCharacters));
                }
                sb.Append(BuildLimitedWidthString(
                    "~",
                    '|', '|', maxCharacters));

                sb.Append(BuildLimitedWidthString(
                        (Config.Instance.ForumExportConfig.ExportRemoveTags ? RemoveTags(msg.Body) : msg.Body),
                        '|',
                        '|', maxCharacters));

                sb.Append(BuildLimitedWidthString(
                    "_",
                    '|', '|', maxCharacters));

                sb.Append(BuildLimitedWidthString(
                    " ",
                    ' ', ' ', maxCharacters));

                i++;
                progressDelegate(i, msgs.Count);
            }

            var encoding = Encoding.Default;
            switch (Config.Instance.ForumExportConfig.ExportPlainTextEncoding)
            {
                case ExportPlainTextEncoding.Default:
                    encoding = Encoding.Unicode; // Default;
                    break;
                case ExportPlainTextEncoding.DOSCP866:
                    encoding = Encoding.GetEncoding(866);
                    break;
                case ExportPlainTextEncoding.Unicode:
                    encoding = Encoding.Unicode;
                    break;
                case ExportPlainTextEncoding.UTF8:
                    encoding = Encoding.UTF8;
                    break;
                case ExportPlainTextEncoding.WinCP1251:
                    encoding = Encoding.GetEncoding(1251);
                    break;
            }

            using (StreamWriter streamWriter = new StreamWriter(stream, encoding))
            {
                streamWriter.Write(sb);
                streamWriter.Flush();
            }
        }

        #region Окантовка текста в строки
        /// <summary>
        /// Делает из строки любой длины &gt;= 1 строки, длиной не более maxLength.
        /// </summary>
        /// <param name="sourceString">The source string.</param>
        /// <param name="leftChar">The left char.</param>
        /// <param name="rightChar">The right char.</param>
        /// <param name="maxLength">Maximal string Length (width) per line.</param>
        /// <returns></returns>
        private static string BuildLimitedWidthString(string sourceString, char leftChar, char rightChar, int maxLength)
        {
            var charInLine = 0;
            var strLength = maxLength - 2;
            var result = new StringBuilder();

            result.Append(leftChar);

            if (sourceString.Length == 1)
            {
                result.Append(new string(sourceString[0], strLength));
            }
            else
            {
                for (int pos = 0; pos < sourceString.Length; pos++)
                {
                    if (sourceString[pos] != '\r' && sourceString[pos] != '\t')
                    {
                        if (pos == sourceString.Length - 1 && sourceString[pos] != '\n')
                        {
                            result.Append(sourceString[pos]);
                            charInLine++;
                        }

                        if (sourceString[pos] == '\n' || pos == sourceString.Length - 1)
                        {
                            for (int i = 0; i < strLength - charInLine; i++)
                            {
                                result.Append(" ");
                            }

                            charInLine = strLength;
                        }
                        else
                        {
                            result.Append(sourceString[pos]);
                            charInLine++;
                        }

                        if (charInLine == strLength && pos != sourceString.Length - 1)
                        {
                            result.Append(rightChar + NewLine + leftChar);
                            charInLine = 0;
                        }
                    }
                }
            }

            result.Append(rightChar + NewLine);
            return result.ToString();
        }
        #endregion

        #region Срез тэгов
        /// <summary>
        /// Срез или замена [] [/] тегов.
        /// </summary>
        /// <param name="sourceString">The source string.</param>
        /// <returns></returns>
        private static string RemoveTags(string sourceString)
        {
            sourceString = RemoveTag(sourceString, "tagline", "");
            sourceString = RemoveTag(sourceString, "q", "--===--");
            sourceString = RemoveTag(sourceString, "img", "");
            sourceString = RemoveTag(sourceString, "c#", "");
            sourceString = RemoveTag(sourceString, "b", "_");
            sourceString = RemoveTag(sourceString, "i", "~");
            sourceString = RemoveTag(sourceString, "msil", "");
            sourceString = RemoveTag(sourceString, "list", "");
            sourceString = RemoveTag(sourceString, "*", "");
            sourceString = RemoveTag(sourceString, "midl", "");
            sourceString = RemoveTag(sourceString, "asm", "-<>-");
            sourceString = RemoveTag(sourceString, "ccode", "-<>-");
            sourceString = RemoveTag(sourceString, "code", "-<>-");
            sourceString = RemoveTag(sourceString, "pascal", "-<>-");
            sourceString = RemoveTag(sourceString, "vb", "-<>-");
            sourceString = RemoveTag(sourceString, "sql", "-<>-");
            sourceString = RemoveTag(sourceString, "java", "-<>-");
            sourceString = RemoveTag(sourceString, "perl", "-<>-");
            sourceString = RemoveTag(sourceString, "email", "");
            sourceString = RemoveTag(sourceString, "msdn", "");
            sourceString = RemoveTag(sourceString, "php", "-<>-");
            sourceString = RemoveTag(sourceString, "hr", "-----===-----");
            sourceString = RemoveTag(sourceString, "moderator", "!!!");

            return sourceString;
        }

        //если есть тут чтото типа #define RemoveTag(SourceString, Tag, StandOut) SourceString = SourceString.Replace("["Tag"]",StandOut); SourceString = SourceString.Replace("[/"Tag"]",StandOut);
        //то думаю лучче так сделать
        /// <summary>
        /// Removes the tag.
        /// </summary>
        /// <param name="sourceString">The source string.</param>
        /// <param name="tag">The tag.</param>
        /// <param name="standOut">The stand out.</param>
        /// <returns></returns>
        private static string RemoveTag(string sourceString, string tag, string standOut)
        {
            sourceString = sourceString.Replace("[" + tag + "]", standOut);
            sourceString = sourceString.Replace("[/" + tag + "]", standOut);

            return sourceString;
        }
        #endregion

        #endregion

        #region Экспорт в html

        #region Собственно в html
        private static void Export2HTML(IServiceProvider provider, IList<IMsg> msgs, Stream stream, ProgressDelegate progressDelegate)
        {
            Encoding encoding = Encoding.UTF8; //GetEncoding(1251);

            using (StreamWriter streamWriter = new StreamWriter(stream, encoding))
            {
                string htmlPage = BuildHTMLPage(provider, msgs, progressDelegate, false, encoding); // was Ut8
                streamWriter.Write(htmlPage);
            }
        }

        #endregion

        #region В mht "архив"
        // Прим. Content-Location настроен на папку картинок стандартного форматтера.
        private const string _mhtContentImageHeader =
            @"

------=_NextPart_
Content-Type: {0}; name={1}.{2}
Content-Location: /Forum/Images/{1}.{2}
Content-Transfer-Encoding: Base64

";

        private const string _mhtHeader =
            @"From: <Сохранено RSDN@Home>
Subject: RSDN@Home
Date: {0}
MIME-Version: 1.0
Content-Type: multipart/related; boundary=""----=_NextPart_""
X-MimeOLE: Produced By RSDN@Home Message Exporter

This is a multi-part message in MIME format.

------=_NextPart_
Content-Type: text/html; charset=""{1}""
Content-Transfer-Encoding: binary

";

        /// <summary>
        /// Export to the MHT format.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="msgs">The MSGS.</param>
        /// <param name="fs">The stream.</param>
        /// <param name="progressDelegate">The progress Delegate.</param>
        private static void Export2Mht(
            IServiceProvider provider,
            IList<IMsg> msgs,
            Stream fs,
            ProgressDelegate progressDelegate)
        {
            using (var sw = new StreamWriter(fs, Encoding.Default))
            {
                Encoding encoding = Encoding.UTF8; //GetEncoding(1251);
                sw.Write(string.Format(_mhtHeader,
                    DateTime.Now.ToString("ddd, d MMM yyyy h:m:s zz00",
                        CultureInfo.InvariantCulture.DateTimeFormat),
                    encoding.WebName));
                sw.Flush();

                string htmlText = BuildHTMLPage(provider, msgs, progressDelegate, true, encoding);
                var page = encoding.GetBytes(htmlText);
                fs.Write(page, 0, page.Length);

                /* Здесь надобы поиск по странице - Page
                 * надо найти какие картинки встречаются - типа smile.gif.., 
                 * (я в таком поиске не силен.... :( )
                 * создать список таких файлов и добалять тока их.
                 * Может и надо, но пока все таки запихивать весь каталог images не стоит, 
                 * смайлов там едва половина
                 */

                const string prefix = @"ForumImages\";

                foreach (string smileName in _smiles)
                {
                    IStyleImageManager styleImageManager = provider.GetRequiredService<IStyleImageManager>();
                    Image smileImage = styleImageManager.GetImage(prefix + smileName, StyleImageType.ConstSize);

                    ImageFormatInfo imageFormatInfo = ImageFormatInfo.FromImageFormat(smileImage.RawFormat);

                    sw.Write(
                        string.Format(
                            _mhtContentImageHeader, imageFormatInfo.MimeType, smileName, imageFormatInfo.Extension));

                    using (MemoryStream ms = new MemoryStream())
                    {
                        smileImage.Save(ms, smileImage.RawFormat);
                        sw.Write(Convert.ToBase64String(ms.ToArray(), Base64FormattingOptions.InsertLineBreaks));
                    }
                }
            }
        }
        #endregion

        #region Создание страницы
        private const string _resourcePrefix = "Rsdn.Janus.Features.ForumViewer.Export.";
        private const string _exportForumResource = _resourcePrefix + "forum.css";
        private const string _exportPageResource = _resourcePrefix + "page.html";
        private const string _messageFormatResource = _resourcePrefix + "MessageFormat.html";
        private const string NewLine = "\r\n";

        /// <summary>
        /// Builds the HTML page.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="msgs">The MSGS.</param>
        /// <param name="progressDelegate">The progress Delegate.</param>
        /// <param name="processSmiles">if set to <c>true</c> [process smiles].</param>
        /// <param name="encoding">The encoding.</param>
        /// <returns></returns>
        private static string BuildHTMLPage(IServiceProvider provider, IList<IMsg> msgs, ProgressDelegate progressDelegate, bool processSmiles, Encoding encoding)
        {
            TextFormatter formatter = new TextFormatter();
            StringBuilder stringBuilder = new StringBuilder();
            Forum forum = new Forum(provider);
            if (msgs.Count > 0)
            {
                forum.LoadData(msgs[0].ForumID);

                stringBuilder.AppendFormat(
                    @"<tr><td class='s' colspan='2' align='center'>{0}&nbsp;<font size='1'>[{1}]</font></td></tr>",
                    forum.Description,
                    forum.Name);
            }
            string messageFormat;
            Assembly executingAssembly = Assembly.GetExecutingAssembly();
            Stream messageTemplate = executingAssembly.GetRequiredResourceStream(_messageFormatResource);
            using (StreamReader streamReader = new StreamReader(messageTemplate))
            {
                messageFormat = streamReader.ReadToEnd();
            }

            int i = 0;
            foreach (IMsg msg in msgs)
            {
                string formattedRating = msg.GetFormattedRating();

                string args = string.IsNullOrEmpty(formattedRating)
                                  ? string.Empty
                                  : SR.TGColumnRate + " " + formattedRating;

                stringBuilder.AppendFormat(messageFormat,
                    msg.ID,
                    msg.Subject,
                    msg.ParentID,
                    SR.Forum.ExportMessages.Export2ParentLink,
                    msg.UserNick,
                    FormatUserClass(provider, (UserClass)msg.UserClass, true),
                    msg.Date,
                    args,
                    formatter.Format(msg.Body, processSmiles));

                i++;
                progressDelegate(i, msgs.Count);
            }

            string exportPageFormat;
            Stream pageTemplate = executingAssembly.GetRequiredResourceStream(_exportPageResource);
            using (StreamReader streamReader = new StreamReader(pageTemplate))
            {
                exportPageFormat = streamReader.ReadToEnd();
            }

            string exportForumStyle;
            Stream cascadeStyleSheet = executingAssembly.GetRequiredResourceStream(_exportForumResource);
            using (StreamReader streamReader = new StreamReader(cascadeStyleSheet))
            {
                exportForumStyle = streamReader.ReadToEnd();
            }

            return string.Format(
                exportPageFormat,
                forum.Description + " [" + forum.Name + "]",
                encoding.HeaderName,
                exportForumStyle,
                stringBuilder);
        }
        #endregion

        #endregion
    }
}